package com.qs.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qs.constant.RedisConstant;
import com.qs.exception.ServiceException;
import com.qs.mapper.GoodsMapper;
import com.qs.mapper.PayInfoMapper;
import com.qs.mapper.StoreMapper;
import com.qs.pojo.dto.CartDto;
import com.qs.pojo.dto.GoodsDto;
import com.qs.pojo.dto.GoodsOrderDto;
import com.qs.pojo.entity.Cart;
import com.qs.mapper.CartMapper;
import com.qs.pojo.entity.Goods;
import com.qs.pojo.entity.PayInfo;
import com.qs.pojo.entity.Store;
import com.qs.pojo.enums.PayStatus;
import com.qs.pojo.vo.GoodsStockLockVo;
import com.qs.response.ResponseEnum;
import com.qs.service.IAsyncService;
import com.qs.service.ICartService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * <p>
 * 购物车表 服务实现类
 * </p>
 *
 * @author author
 * @since 2023-12-03
 */
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {

    private final GoodsMapper goodsMapper;

    private final StoreMapper storeMapper;

    private final RedisTemplate redisTemplate;

    private final RedissonClient redissonClient;

    private final PayInfoMapper payInfoMapper;

    private final IAsyncService asyncService;

    @Override
    public void addCart(Cart cart) {
        //先查询商品库存是否够
        Goods goods = goodsMapper.selectById(cart.getGoodsId());
        if( goods != null){
            if( goods.getNumber() < cart.getNumber()){
                throw new ServiceException(ResponseEnum.NUMBER_LOW);
            }
            //库存充足的情况下保证线程安全
            ReentrantLock lock = new ReentrantLock();
            lock.lock();
            try {
                baseMapper.insert(cart);
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }

    @Override
    public Map<String,Object> getList(Integer userId) {
        //根据用户id查询购物车
        QueryWrapper<Cart> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id",userId);
        wrapper.orderByDesc("update_time");
        List<Cart> carts = baseMapper.selectList(wrapper);
        if(!CollectionUtils.isEmpty(carts)){
            List<CartDto> list = new ArrayList<CartDto>();
            carts.stream().forEach(item -> {
                CartDto cartDto = new CartDto();
                Goods goods = goodsMapper.selectById(item.getGoodsId());
                BeanUtils.copyProperties(item,cartDto);
                cartDto.setName(goods.getName());
                cartDto.setKey(item.getId());
                cartDto.setImage(goods.getImage());
                cartDto.setPrice(goods.getPrice());
                cartDto.setTotalPrice(item.getNumber() * goods.getPrice());
                list.add(cartDto);
            });
            Map<String,Object> map = new HashMap<String,Object>(list.size());
            map.put("data",list);
            map.put("total",list.size());
            return map;
        }
        return null;
    }

    @Override
    public void updateCartById(Integer id, Integer number) {
        Cart cart = baseMapper.selectById(id);
        cart.setNumber(number);
        baseMapper.updateById(cart);
    }

    @Override
    public Map<String,Object> getOrderGoods(List<Integer> ids) {
        if(!CollectionUtils.isEmpty(ids)){
            List<GoodsDto> list = new ArrayList<GoodsDto>(ids.size());
            ids.stream().forEach(cartId -> {
                Cart cart = baseMapper.selectById(cartId);
                GoodsDto goodsDto = new GoodsDto();
                //获取商品
                Goods goods = goodsMapper.selectById(cart.getGoodsId());
                //获取商品对应的店铺
                Store store = storeMapper.selectById(goods.getStoreId());
                goodsDto.setKey(goods.getId());
                goodsDto.setStoreId(store.getId());
                goodsDto.setNumber(cart.getNumber());
                goodsDto.setStoreImage(store.getImage());
                goodsDto.setHomeName(store.getHomeName());
                goodsDto.setBrand(goods.getBrand());
                goodsDto.setImage(goods.getImage());
                goodsDto.setCategory(goods.getCategory());
                goodsDto.setName(goods.getName());
                goodsDto.setPrice(goods.getPrice());
                goodsDto.setSubTotalPrice(goods.getPrice() * cart.getNumber());
                goodsDto.setStatus(goods.getStatus());
                list.add(goodsDto);
            });
            double totalPrice = 0.0;
            for (int i = 0; i < list.size(); i++) {
                totalPrice += list.get(i).getSubTotalPrice();
            }
            Map<String,Object> map = new HashMap<>();
            map.put("data",list);
            map.put("totalPrice",totalPrice);
            return map;
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String getOrderId(List<Integer> cartIds,Integer userId) {
        //生成一个唯一的订单id
        String orderId = IdUtil.getSnowflakeNextIdStr();
        redisTemplate.opsForValue().set(RedisConstant.ORDER_ID + orderId,orderId,RedisConstant.ORDER_CACHE_EXPIRE,TimeUnit.MINUTES);
        //获取订单中的所有商品数据
        if(!CollectionUtils.isEmpty(cartIds)){
            List<GoodsStockLockVo> stockLockVos = new ArrayList<GoodsStockLockVo>(cartIds.size());
            cartIds.stream().forEach(id -> {
                GoodsStockLockVo goodsStockLockVo = new GoodsStockLockVo();
                Cart cart = baseMapper.selectById(id);
                Goods goods = goodsMapper.selectById(cart.getGoodsId());
                goodsStockLockVo.setGoodsId(goods.getId());
                goodsStockLockVo.setNumbers(cart.getNumber());
                stockLockVos.add(goodsStockLockVo);
            });
            //验证和锁定库存
            boolean isLockSuccess = this.checkAndLock(stockLockVos, orderId);
            if(!isLockSuccess){
                //库存锁定失败
                throw new ServiceException(ResponseEnum.GOODS_STOCK_FAIL);
            }
            //将支付表的状态设置为待付款
            asyncService.savePayInfo(orderId, userId);
            //保存订单和购物车记录
            asyncService.saveOrderCart(orderId, userId,cartIds);
            //库存锁定成功返回订单id
            return orderId;
        }
        return null;
    }

    /**
     * 验证和锁定库存
     * @param StockLockList
     * @param orderId
     * @return
     */
    private boolean checkAndLock(List<GoodsStockLockVo> StockLockList,String orderId) {
        //判空
        if(CollectionUtils.isEmpty(StockLockList)){
            throw new ServiceException(ResponseEnum.GOODS_STOCK_FAIL);
        }
        //遍历每一个商品，验证并锁定库存，具备原子性
        StockLockList.stream().forEach(item -> {
            this.checkLock(item);
        });

        //只要有一个商品库存锁定失败，所有锁定成功的商品都解锁
        boolean flag = StockLockList.stream().anyMatch(item -> !item.isLock());
        if(flag){
            //所有锁定成功的商品都解锁
            StockLockList.stream().filter(GoodsStockLockVo::isLock)
                    .forEach(stockLockVo -> {
                        //解锁库存
                        goodsMapper.unLockStock(stockLockVo.getGoodsId(),stockLockVo.getNumbers());
                    });
            //返回失败状态
            return false;
        }
        //如果所有商品都锁定成功，则缓存锁定库存数据，方便后续解锁和减库存
        //TODO: 缓存设置时间为30
        redisTemplate.opsForValue().set(RedisConstant.GOODS_STOCK_LOCK + orderId,StockLockList);
        return true;
    }

    /**
     * 锁定库存逻辑方法
     * @param stockLockVo
     * @return
     */
    private void checkLock(GoodsStockLockVo stockLockVo) {
        //获取锁
        //TODO: redis的分布式锁：公平锁
        RLock rLock = this.redissonClient.getFairLock(RedisConstant.STOCK_LOCK + stockLockVo.getGoodsId());
        //上锁
        rLock.lock();
        try {
            //验证库存
            Goods goods = goodsMapper.checkStock(stockLockVo.getGoodsId(),stockLockVo.getNumbers());
            if(null == goods){
                //说明库存不足，锁定失败
                stockLockVo.setLock(false);
                return;
            }
            //锁定库存
            //就是一个更新操作
            Integer rows = goodsMapper.lockStock(stockLockVo.getGoodsId(),stockLockVo.getNumbers());
            if(rows >= 1){
                //成功锁定
                stockLockVo.setLock(true);
            }
        }finally {
            //解锁
            rLock.unlock();
        }
    }
}
